package com.ziiwee.reduce.rest.auto;

import com.ziiwee.reduce.commons.utils.ReflectUtil;
import com.ziiwee.reduce.commons.utils.StringUtil;
import com.ziiwee.reduce.rest.Resource;
import com.ziiwee.reduce.rest.ResourceService;
import com.ziiwee.reduce.rest.ResourceServiceException;
import com.ziiwee.reduce.rest.condition.Condition;
import com.ziiwee.reduce.rest.utils.RestUtil;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import javax.servlet.ServletException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author ziiwee
 * @date 2020/8/18
 */
public class DefaultRestAutoHandlerProvider implements RestAutoHandleProvider {

    private final int defaultPageSize;


    public DefaultRestAutoHandlerProvider() {
        defaultPageSize = new SpringDataWebProperties.Pageable().getDefaultPageSize();
    }

    public DefaultRestAutoHandlerProvider(SpringDataWebProperties springDataWebProperties) {
        this.defaultPageSize = springDataWebProperties.getPageable().getDefaultPageSize();
    }

    @Override
    public ServerResponse render(HttpMethod method, ResourceInfo info, ServerRequest request) throws ServletException, IOException {
        switch (method) {
            case GET:
                return get(info, request);
            case POST:
                return post(info, request);
            case PUT:
                return put(info, request);
            case PATCH:
                return patch(info, request);
            case DELETE:
                return delete(info, request);
            default:
                throw new ResourceServiceException("不支持的 HttpMethod :" + method);
        }
    }

    private ServerResponse get(ResourceInfo info, ServerRequest request) {
        String id;
        try {
            id = request.pathVariable("id");
        } catch (Exception e) {
            id = null;
        }
        if (StringUtil.hasLength(id)) {
            return get(info, request, id);
        }
        int page = Integer.parseInt(request.param("page").orElse("0"));
        int size = Integer.parseInt(request.param("size").orElse(String.valueOf(defaultPageSize)));
        MultiValueMap<String, String> params = request.params();
        List<String> sortList = params.get("sort");
        Sort sort = Sort.unsorted();
        if (sortList != null) {
            for (String sortString : sortList) {
                if (sortString.contains(",")) {
                    String[] split = sortString.split(",");
                    Sort.Direction direction = Sort.Direction.fromOptionalString(split[1]).orElse(Sort.DEFAULT_DIRECTION);
                    if (StringUtil.hasLength(split[0])) {
                        sort.and(Sort.by(direction, split[0]));
                    }
                } else {
                    sort.and(Sort.by(Sort.DEFAULT_DIRECTION, sortString));
                }
            }
        }
        PageRequest pageable = PageRequest.of(page, size, sort);
        Condition condition = RestUtil.parseCondition(info.getType(), request.servletRequest().getQueryString());
        Page<?> all = info.getService().findAll(pageable, condition);
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(all);
    }

    private ServerResponse get(ResourceInfo info, ServerRequest request, String id) {
        Object castId = ReflectUtil.castValue(info.getIdType(), id);
        Resource<Object> one = asService(info.getService()).findOne(castId);
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(one);
    }

    private ServerResponse post(ResourceInfo info, ServerRequest request) throws ServletException, IOException {
        Object body = request.body(info.getType());
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Set<ConstraintViolation<Object>> validate = validatorFactory.getValidator().validate(body);
        if (validate.size() > 0) {
            throw new ConstraintViolationException(validate);
        }
        Resource<Object> one = asService(info.getService()).create(asResource(body));
        return ServerResponse.ok().body(one);
    }

    private ServerResponse put(ResourceInfo info, ServerRequest request) throws ServletException, IOException {
        return patch(info, request);
    }

    private ServerResponse patch(ResourceInfo info, ServerRequest request) throws ServletException, IOException {
        String id = request.pathVariable("id");
        Object castId = ReflectUtil.castValue(info.getIdType(), id);
        Map<String, Object> body = request.body(new ParameterizedTypeReference<Map<String, Object>>() {
        });
        Resource<Object> update = asService(info.getService()).update(castId, body);
        return ServerResponse.ok().body(update);
    }

    private ServerResponse delete(ResourceInfo info, ServerRequest request) {
        String id = request.pathVariable("id");
        Object castId = ReflectUtil.castValue(info.getIdType(), id);
        asService(info.getService()).delete(castId);
        return ServerResponse.ok().body("");
    }

    @SuppressWarnings("unchecked")
    private ResourceService<Resource<Object>, Object> asService(ResourceService<?, ?> service) {
        return (ResourceService<Resource<Object>, Object>) service;
    }

    @SuppressWarnings("unchecked")
    private Resource<Object> asResource(Object resource) {
        return (Resource<Object>) resource;
    }

}
